Edycja danych
Przez edycję danych rozumiemy takie działania jak przyjmowanie zamówień, obsługa pozycji zamówienia, rezerwacje stolików, rejestracja gości i tak dalej. Każde pojedyncze działanie powoduje niewielką, precyzyjną zmianę, na przykład AddOrderItemProduct dodaje pozycję do zamówienia, natomiast AddOrderItemModifier dodaje modyfikator do pozycji. Wiele pojedynczych działań może prowadzić do niespójności danych, na przykład jeśli pozycja wymaga modyfikatorów, to dodanie jej bez modyfikatorów narusza odpowiednią regułę biznesową. Użytkownicy mogą dodawać pozycję do zamówienia wraz z modyfikatorami, łącząc działania. W takim przypadku ważne jest, aby działania odbywały się zgodnie z zasadą «Wszystko albo nic» i przekształcały dane ze stanu spójnego do innego stanu spójnego. Aby zapewnić transakcyjność podczas edycji danych, wprowadzamy pojęcie sesji edycyjnej.
Sesja edycyjna
Sesja edycyjna jest rodzajem transakcji bazodanowej. Wszystkie działania, nawet pojedyncze, są wykonywane w ramach sesji w następujący sposób:
IEditSessionjest uruchamiana przez wywołaniePluginContext.Operations.CreateEditSession.- Jedno lub więcej działań jest wykonywanych w ramach sesji.
- Wprowadzone zmiany są zapisywane za pomocą metody
PluginContext.Operations.SubmitChanges.
Zmiany dokonane w kroku drugim nie są widoczne aż do momentu zapisu. W kroku trzecim wszystkie zmiany są albo zapisane pomyślnie, albo wycofane, a w przypadku błędu generowany jest wyjątek.
Synchronizacja
Dostęp do danych jest synchronizowany za pomocą blokad i rewizji. Obiekty są blokowane podczas edycji i nie jest możliwe bezpośrednie zarządzanie tymi blokadami przez API, ponieważ obiekty są blokowane automatycznie w momencie zapisu zmian (SubmitChanges). Jest to zgodne z koncepcją blokad optymistycznych: podczas tworzenia sesji edycyjnej i wykonywania działań obiekty nie są blokowane, natomiast podczas zapisu zmian następuje weryfikacja, czy dane nie zostały zmienione przez innego użytkownika. Po zapisaniu obiekty otrzymują nową rewizję, co pozwala odróżnić różne wersje obiektu. Biorąc pod uwagę niewielkie zapotrzebowanie na równoczesną edycję tych samych obiektów, takie podejście upraszcza interfejs użytkownika (nie wymaga metod zarządzania blokadami, nie trzeba martwić się o ich odpowiednie zwolnienie) i zapewnia dostępność danych (obiekt nie może być zablokowany na długo, blokady nie mogą pozostać niezwolnione, w przypadku awaryjnego zamknięcia wtyczki obiekt nie zablokuje się na stałe).
Należy pamiętać o pewnych specyfikach programistycznych:
- Sama aplikacja Syrve POS może blokować obiekty na długi czas (blokady pesymistyczne). Na przykład, gdy użytkownik przechodzi do ekranu edycji zamówienia, odpowiednie zamówienie będzie zablokowane co najmniej do momentu zamknięcia tego ekranu (następnie zależy to od kolejnego ekranu, na który przejdzie użytkownik). W tym czasie zablokowanego zamówienia nie można edytować przez API. Kelnerzy powinni być prawdopodobnie poinformowani, aby blokować ekran, gdy opuszczają POS lub ustawić krótki czas automatycznego blokowania (domyślnie 10 minut). Poza tym obiekty mogą być zablokowane przez pewien czas niezależnie od aktywności użytkownika (na przykład niektóre zmiany mogą być zaplanowane).
- Gdy obiekt jest zablokowany, inne powiązane obiekty również zostaną zablokowane. Tak więc blokując zamówienie bankietowe, zablokowana zostanie również odpowiednia rezerwacja. Jeśli choć jeden obiekt nie może zostać zablokowany, operacja kończy się niepowodzeniem.
- Aby poprawnie synchronizować dostęp do danych, używaj Syrve Office do ustawienia głównej kasy w ustawieniach grupy. Wtyczka korzystająca z funkcji edycji danych powinna być najlepiej zainstalowana na głównej kasie. Inne terminale również mogą być używane, ale w niektórych scenariuszach zmiany są odrzucane, co wynika ze szczegółów implementacji. Począwszy od V9Preview3 to ograniczenie zostało usunięte.
Wykonywanie ciągłe
Operacje IOperationService, które wymagają synchronizacji podczas edycji danych. Blokują i odblokowują obiekty przy każdym wywołaniu. Dlatego gdy kilka operacji jest wywoływanych kolejno, każda operacja blokuje dane niezależnie, dokonuje zmian, a następnie je odblokowuje. Inne podmioty mogą wtrącić się pomiędzy wywołaniami operacji z zamiarem edycji tych samych danych; w efekcie niektóre operacje zostaną wykonane pomyślnie, a inne zwrócą błędy EntityAlreadyInUseException, EntityModifiedException, a inne operacje mogą stać się nieaktualne ze względu na inne zmiany.
Na przykład, zamówienie dostawy z opublikowaną zewnętrzną przedpłatą musi zostać utworzone w wtyczce, która przyjmuje zewnętrzne zamówienia dostawy (strona internetowa, agregator). Wszystko to nie może być wykonane atomowo w jednej sesji edycyjnej, ponieważ zatwierdzenie płatności jest nieodwracalne i wykonywane osobno, dlatego najpierw trzeba utworzyć zamówienie dostawy i wypełnić jego pola, dodając niezatwierdzoną zewnętrzną przedpłatę (CreateEditSession, CreateDeliveryOrder, AddExternalPaymentItem itd.), zapisać zmiany (SubmitChanges), a następnie spróbować zatwierdzić przedpłatę (ProcessPrepay). Dane pozostają odblokowane pomiędzy tymi operacjami (SubmitChanges i ProcessPrepay) i są dostępne do edycji dla każdego. Osoby subskrybujące zmiany dostawy mogą zostać powiadomione o nowym zamówieniu dostawy na czas, aby wprowadzić zmiany przed zatwierdzeniem przedpłaty. Pisanie kodu, który może zostać przerwany, a następnie działać płynnie, dostosowując się do zewnętrznych zmian, jest dość czasochłonne. Aby takie scenariusze działały płynnie, dodaliśmy możliwość wykonywania kilku operacji nieprzerwanie lub ciągle.
ExecuteContinuousOperation — specjalna operacja, która może być używana do sukcesywnego i ciągłego wykonywania kilku innych operacji. Operacje powinny być pogrupowane w jedną funkcję lub lambdę w kodzie wtyczki i przesłane jako callback do metody ExecuteContinuousOperation, która wywoła ten callback i przekaże do niego specjalną instancję serwisu IOperationService przeznaczoną do ciągłego wykonywania operacji:
PluginContext.Operations.ExecuteContinuousOperation(
operations =>
{
...
operations.SubmitChanges(...);
...
operations.ProcessPrepay(...);
...
});
Należy zauważyć, że operacja główna ExecuteContinuousOperation jest wywoływana za pomocą ogólnej usługi PluginContext.Operations, natomiast operacje włączone — za pomocą instancji usługi otrzymanej przez lambdę na wejściu (w przykładzie powyżej nazywanej operations). Technicznie rzecz biorąc, operacje wywoływane przez specjalną instancję usługi działają tak samo, ale nie odblokowują danych po ich wykonaniu, co oznacza, że każda operacja wymagająca synchronizacji blokuje dane, jeśli nie zostały one wcześniej zablokowane przez inne operacje, dokonuje zmian i pozostawia dane zablokowane dla kolejnych operacji. Zapewnia to, że żadna inna jednostka nie może „ukraść” blokady i wtrącić się, a nasze kolejne operacje na tych samych obiektach nie otrzymają wyjątku EntityAlreadyInUseException. Dane zostaną odblokowane, gdy kontrola zostanie zwrócona z lambdy.
Należy wziąć pod uwagę następujące ograniczenia:
- Ta funkcja nie ma nic wspólnego z atomowością ani transakcyjnością, każda wbudowana operacja jest wykonywana indywidualnie i natychmiast zapisuje zmiany. Jeśli którakolwiek z operacji zakończy się niepowodzeniem (wygenerowany wyjątek), wcześniejsze operacje nie zostaną anulowane.
- Kontynuacja operacji nie oznacza, że żadna inna strona nie może nic zrobić. Oznacza to, że spójna edycja konkretnego obiektu nie może zostać przerwana. Inne wtyczki i aplikacja Syrve POS mogą jednocześnie edytować inne obiekty. Takie obiekty nie powinny być przez nas blokowane ani zmieniane. Dane są blokowane w razie potrzeby, dlatego jeśli zdecydujemy się zmienić obiekt podczas wykonywania ciągłej sekwencji po pomyślnym wykonaniu operacji na innym obiekcie, prawdopodobnie otrzymamy wyjątek
EntityAlreadyInUseException. - Ponieważ dane objęte operacją pozostają zablokowane przez cały czas działania lambdy przekazanej do
ExecuteContinuousOperation, co może utrudniać działania użytkownika, innych wtyczek i funkcji aplikacji, sekwencja operacji powinna być wykonywana szybko. W trakcie ciągłej sesji wykonywania, zapytania do zewnętrznych usług i sprzętu powinny być wykonywane ostrożnie, bez zbędnego zwlekania lub najlepiej unikać zewnętrznego I/O. Jeśli to możliwe, przygotowania wykonuj z wyprzedzeniem poza ciągłą sesją wykonywania. Jeśli to niemożliwe, upewnij się, że istnieją rozsądne limity bezczynności.
Stuby
Ponieważ przed zapisaniem całej sesji nie są dostępne wyniki działań, konieczne jest odwoływanie się do nowego, ale jeszcze nieistniejącego obiektu podczas wykonywania sekwencji operacyjnej w jednej sesji. Na przykład użytkownicy mogą chcieć dodać gości do zamówienia, następnie dodać pozycje do gościa i modyfikatory do pozycji, mimo że nie ma jeszcze zamówienia, gości ani pozycji. Z tego powodu wprowadzamy koncepcję stubów — pewnych fałszywych, ale jednoznacznych referencji do obiektów. Akcje tworzenia obiektów, takie jak CreateOrder lub AddOrderGuest zwracają stuby INew...Stub, które mogą być używane zamiast innych obiektów w tej samej sesji.
Większość metod edycyjnych przyjmuje takie stuby jako argumenty, co umożliwia przekazywanie zarówno istniejących, jak i nowych obiektów do tych metod. Na przykład metoda SetOrderType przyjmuje IOrderStub, dzięki czemu typ można ustawić zarówno dla istniejącego zamówienia (IOrder : IOrderStub), jak i nowego zamówienia (INewOrderStub : IOrderStub).
Jednak niektóre akcje mogą wymagać ściśle jednego z dwóch — nowego lub istniejącego obiektu, w takim przypadku w sygnaturze metody będzie używany jeden z potomków (zamiast typu bazowego).
Oczekiwane wyjątki
Podczas zapisywania zmian mogą wystąpić różne wyjątki. Niektóre z nich mogą wskazywać na błąd w kodzie wtyczki (na przykład ArgumentNullException lub ArgumentOutOfRangeException); nie zalecamy tłumienia takich wyjątków (lepiej jest poprawić błąd w kodzie). Jednak niektóre wyjątki nie mogą być przewidziane ani zapobiegane — należy je raczej przechwycić i odpowiednio obsłużyć:
EntityAlreadyInUseException— próba zastosowania zmian do zablokowanego obiektu. Można spróbować ponownie później.EntityModifiedException— próba zastosowania zmian do starej wersji obiektu. Oznacza to, że po odczytaniu obiektu przez wtyczkę, obiekt ten został zmieniony przez inną stronę. Obiekt należy ponownie odczytać; a jeśli zaplanowane zmiany są nadal potrzebne, należy je ponownie zastosować.PermissionDeniedException— próba wykonania jakiejkolwiek akcji bez odpowiednich uprawnień. Jeśli użytkownik chce, aby wtyczka wykonała takie akcje, musi zostać jej przyznane odpowiednie uprawnienia za pomocą Syrve Office.- ...
Syntactic Sugar
Czasami zdarza się, że wymagana jest tylko jedna akcja, w takim przypadku tworzenie sesji edycyjnej wydaje się uciążliwe. Z tego powodu IOperationService posiada pomocnicze metody rozszerzające, które tworzą sesję edycyjną, wykonują wymaganą akcję, zapisują zmiany i zwracają wynik akcji. Technicznie rzecz biorąc, to samo można napisać ręcznie. Nie zalecamy używania takich opakowań, jeśli ma być wykonanych więcej niż jedna akcja jednocześnie.